/***
* Feel free to use this code however you wish.
* Created by Alie Tan (www(dot)alietan(dot)com)
*/

#include <ma.h>
#include <mastdlib.h>
#include <MAUtil/FrameBuffer.h>
#include <MAUtil/Moblet.h>

#include <MoBox2D/Box2D.h>

#include "Sprite.h"
#include "MAHeaders.h"

#define SCALE 1.0

// Ahhh!, nothing like dangerous, multi-valued stuff...  But it's handy!
#define SCREEN_TO_PHYS(x, y) (x)/SCALE, (y)/SCALE
#define PHYS_TO_SCREEN(x, y) (x)*SCALE, (y)*SCALE

#define TIMER_PERIOD 1

using namespace MAUtil;


class MyMoblet : public Moblet, TimerListener
{
public:
	MyMoblet()
	{
		// Get screen dimensions.
		MAExtent size = maGetScrSize();
		// Extract the screen width
		screenWidth = EXTENT_X(size);
		// Extract the screen height
		screenHeight = EXTENT_Y(size);

		// Create the image in which the cube will be drawn, to draw multiple versions of it.
		maCreateDrawableImage(1, screenWidth, screenHeight);

		initWorld();
		initBall();

		addTimer(this, TIMER_PERIOD, 0);
	}

	~MyMoblet()
	{
	    delete world;
	    delete ballSprite;
	}

	void runTimerEvent()
	{
		// Now sim code
	    timeStep = 1.0f / 60.0f;

		// Set the following draw calls to draw to the drawable image created (draw target 1).
    	maSetDrawTarget(1);

    	maSetColor(0x0088ff);
		maFillRect(0, 0, screenWidth, screenHeight);

		world->Step(timeStep, 1, 1);
		for(b2Body *b = world->GetBodyList(); b; b=b->GetNext())
		{
			if (b->GetUserData() != NULL)
			{
				CSprite *sprite = (CSprite *)b->GetUserData();
				int width = sprite->getWidth();
				int height = sprite->getHeight();
				int x = (int)(b->GetPosition().x*SCALE - width/2);
				int y = (int)(b->GetPosition().y*SCALE - height/2);
				float32 angle = b->GetAngle();
				void *texture = sprite->getData();

				MARect srcRect = {0, 0, width, height};
				MAPoint2d dstPoint = {x, y};
				maDrawRGB(&dstPoint, texture, &srcRect, width);
			}
		}

		// Set the following draw calls to draw to the screen (draw target 0).
		maSetDrawTarget(0);

		MARect srcRect = {0, 0, screenWidth, screenHeight};
		MAPoint2d dstPoint = {0, 0};
		maDrawImageRegion(1, &srcRect, &dstPoint, TRANS_NONE);

		/// Updates the screen
		maUpdateScreen();

		/// Keep the backlight alive.
		maResetBacklight();
	}

	void loadBallTexture()
	{
		MAExtent textureSize = maGetImageSize(R_BALL);
		int width = EXTENT_X(textureSize);
		int height = EXTENT_Y(textureSize);
		unsigned int *texture = new unsigned int [width*height];
		MARect srcRect = {0, 0, width, height};
		maGetImageData(R_BALL, texture, &srcRect, width);
		ballSprite = new CSprite(texture, width, height);
	}

	void initBall()
	{
		loadBallTexture();

		srand(maLocalTime());
		for(int i = 0; i < 3; i++)
		{
			// Create ball body and shape
			b2BodyDef ballBodyDef;
			ballBodyDef.type = b2_dynamicBody;
			int x = (screenWidth-100)&rand();
			int y = (screenHeight-100)&rand();
			ballBodyDef.position.Set(50+x, 50+y);

			ballBodyDef.userData = ballSprite;

			b2CircleShape circle;
			circle.m_radius = (ballSprite->getWidth()/2)/SCALE;

			b2FixtureDef ballShapeDef;
			ballShapeDef.shape = &circle;
			ballShapeDef.density = 1.0f;
			ballShapeDef.friction = 0.0f;
			ballShapeDef.restitution = 0.8f;

			b2Body *body = world->CreateBody(&ballBodyDef);
			body->CreateFixture(&ballShapeDef);
		}
	}

	void initWorld()
	{
		// Create a world
		b2Vec2 gravity = b2Vec2(0.0f, 10.0f);
		bool doSleep = true;
		world = new b2World(gravity, doSleep);

		// Create edges around the entire screen
		b2BodyDef groundBodyDef;
		groundBody = world->CreateBody(&groundBodyDef);
		b2PolygonShape groundBox;
		b2FixtureDef boxShapeDef;
		boxShapeDef.shape = &groundBox;
		groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(SCREEN_TO_PHYS(screenWidth, 0)));
		groundBody->CreateFixture(&boxShapeDef);
		groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(SCREEN_TO_PHYS(0, screenHeight)));
		groundBody->CreateFixture(&boxShapeDef);
		groundBox.SetAsEdge(b2Vec2(SCREEN_TO_PHYS(0, screenHeight)), b2Vec2(SCREEN_TO_PHYS(screenWidth, screenHeight)));
		groundBody->CreateFixture(&boxShapeDef);
		groundBox.SetAsEdge(b2Vec2(SCREEN_TO_PHYS(screenWidth, screenHeight)), b2Vec2(SCREEN_TO_PHYS(screenWidth, 0)));
		groundBody->CreateFixture(&boxShapeDef);
	}

	b2Body *getBodyAtMouse(b2Vec2 p)
	{
		b2Body *body = NULL;
		bool finish = false;
		for(b2Body *b = world->GetBodyList(); b&&!finish; b=b->GetNext())
		{
			if(body != groundBody)
			{
				for (b2Fixture* f = b->GetFixtureList(); f; f = f->GetNext())
				{
					if(f->TestPoint(p))
					{
						body = b;
						finish = true;
						break;
					}
				}
			}
		}
		return body;
	}

	void pointerPressEvent(MAPoint2d p)
	{
		b2Body *body = getBodyAtMouse(b2Vec2(p.x, p.y));
	    if (body)
	    {
			b2MouseJointDef mouseJointDef;
			mouseJointDef.bodyA = groundBody;
			mouseJointDef.bodyB = body;
			mouseJointDef.target.Set(p.x, p.y);
			mouseJointDef.maxForce = 1000.0f * body->GetMass();
			mouseJointDef.dampingRatio = 0.0f;
			mouseJointDef.frequencyHz = 60.0f;
			mouseJoint = (b2MouseJoint *)world->CreateJoint(&mouseJointDef);
			body->SetAwake(true);
	    }
	}

	void pointerMoveEvent(MAPoint2d p)
	{
		pointerPos = b2Vec2(p.x, p.y);
		if(mouseJoint)
		{
			mouseJoint->SetTarget(pointerPos);
		}
	}

	void pointerReleaseEvent(MAPoint2d p)
	{
		if (mouseJoint)
		{
			world->DestroyJoint(mouseJoint);
			mouseJoint = NULL;
		}
	}

private:
	int screenWidth;
	int screenHeight;

	float32 timeStep;
	b2Vec2 pointerPos;

	b2World *world;
	b2Body *groundBody;
	b2MouseJoint *mouseJoint;

	CSprite *ballSprite;

};

extern "C" int MAMain()
{
	Moblet::run(new MyMoblet());
}
